#include "sockets.hpp"
#include "sockets_impl.hpp"

namespace vl
{

#if defined(WINDOWS)

int sock_error() { return WSAGetLastError(); }
int const sockerr_in_progress = WSAEINPROGRESS;
int const sockerr_would_block = WSAEWOULDBLOCK;

bool socket::valid()
{
	return ((SOCKET)_voidp) != INVALID_SOCKET;
}

void socket::close()
{
	if(valid())
	{
		closesocket(native_socket(*this));
		_voidp = (void*)(INVALID_SOCKET);
	}
}

socket::socket()
{
	_voidp = (void*)(INVALID_SOCKET);
}

int const sock_error_ret = SOCKET_ERROR;

#else

int const sockerr_in_progress = EINPROGRESS;
int const sockerr_would_block = EWOULDBLOCK;

int sock_error() { return errno; }

bool socket::valid()
{
	return _int != -1;
}

void socket::close()
{
	if(valid())
	{
		::close(native_socket(*this));
		_int = -1;
	}
}

socket::socket()
{
	_int = -1;
}

int const sock_error_ret = -1;

#endif

sockaddr* internet_sockaddr::get_sockaddr()
{
	return reinterpret_cast<sockaddr *>(static_cast<sockaddr_in*>(this));
}



char* copyCString(char* p)
{
	size_t l = strlen(p);
	char* n = new char[l + 1];
	memcpy(n, p, l + 1);
	return n;
}

char* copyString(char* p, size_t l)
{
	char* n = new char[l + 1];
	memcpy(n, p, l + 1);
	return n;
}

char** copyList(char** p)
{
	int l = 0;
	for(; p[l]; ++l)
		/* nothing */;
	
	char** n = new char*[l + 1];
	for(int i = 0; i < l; ++i)
	{
		n[i] = copyCString(p[i]);
	}
	
	n[l] = 0;
	
	return n;
}

char** copyListL(char** p, size_t len)
{
	int l = 0;
	for(; p[l]; ++l)
		/* nothing */;
	
	char** n = new char*[l + 1];
	for(int i = 0; i < l; ++i)
	{
		n[i] = copyString(p[i], len);
	}
	
	n[l] = 0;
	
	return n;
}

void deleteList(char** p)
{
	for(int i = 0; p[i]; ++i)
		delete[] p[i];
	delete[] p;
}

host_entry::host_entry(hostent const* p)
{
	h_name = copyCString(p->h_name);
	h_aliases = copyList(p->h_aliases);
	h_addrtype = p->h_addrtype;
	h_length = p->h_length;
	h_addr_list = copyListL(p->h_addr_list, h_length);
}

host_entry::~host_entry()
{
	delete[] h_name;
	deleteList(h_aliases);
	deleteList(h_addr_list);
}

host_entry* resolve_host(std::string const& name)
{
	hostent* p = gethostbyname( name.c_str() );
	
	if(!p)
		return 0; // ERROR
	
	return new host_entry(p);
}

void delete_host(host_entry* p)
{
	delete p;
}

socket socket_nonblock()
{
	socket s;
	
	s = make_socket(::socket(PF_INET, SOCK_STREAM, 0));
		
	if(!s.valid())
		return socket();

	set_no_blocking(s);

	return s;
}

void set_no_blocking(socket s)
{
#ifdef WINDOWS
	unsigned long non_blocking = 1;
	ioctlsocket(native_socket(s), FIONBIO, &non_blocking);
#else
	fcntl(native_socket(s), F_SETFL, O_NONBLOCK);
#endif
}

bool set_no_delay(socket s, int no_delay)
{
#if !defined(BEOS_NET_SERVER)
	return setsockopt(
		native_socket(s),
		IPPROTO_TCP,
		TCP_NODELAY,
		reinterpret_cast<char const*>(&no_delay),
		sizeof(no_delay)) == 0;
#else
	return true;
#endif
}


bool bind(socket s, int port)
{
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY;
	memset(&(addr.sin_zero), '\0', 8);

	int ret = ::bind(native_socket(s), reinterpret_cast<sockaddr *>(&addr), sizeof(addr));
	return (ret != sock_error_ret);
}

bool listen(socket s)
{
	int ret = ::listen(native_socket(s), 5);
	return (ret != sock_error_ret);
}

socket accept(socket s, internet_sockaddr& addr)
{
#ifdef WINDOWS
	int sin_size = sizeof(sockaddr_in);
#else
	socklen_t sin_size = sizeof(sockaddr_in);
#endif

	// TODO: Check for errors
	return make_socket(accept(native_socket(s), addr.get_sockaddr(), &sin_size));
}

bool connect(socket s, internet_sockaddr& addr)
{
	int r = connect(native_socket(s), addr.get_sockaddr(), sizeof(addr));
	
	if(r == sock_error_ret)
	{
		int err = sock_error();
		
		#ifdef WINDOWS
			if(err != sockerr_would_block)
		#else
			if(err != sockerr_in_progress)
		#endif
				return false; // ERROR
	}
		
	return true;
}

bool create_addr(internet_sockaddr& addr, host_entry* hp, int port)
{
	memset((char *) &addr, 0, sizeof(addr));
    memmove((char *) &addr.sin_addr, hp->h_addr_list[0], hp->h_length);
    addr.sin_family = hp->h_addrtype;
    addr.sin_port = htons( port );
    
    return true;
}

#ifdef WINDOWS

int winsock_ref = 0;

void init_sockets()
{
	if(++winsock_ref == 1)
	{
		WSADATA wsaData;

		int res = WSAStartup(MAKEWORD(2,2), &wsaData);
		if (res != 0) {
			return;
		}
	}
}

void deinit_sockets()
{
	if(--winsock_ref == 0)
	{
		WSACleanup();
	}
}
#endif

}
